package per.cy.common.util;

import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * @author cy
 * date 2024-08-16
 */
public class RSAUtil {

    private static final Log log = LogFactory.get();

    // 定义RSA密钥的长度为2048位
    private static final int RSA_KEY_SIZE = 2048;

    // 指定RSA算法的名称
    private static final String RSA_ALGORITHM = "RSA";


    /**
     * 生成RSA密钥对，并分别保存公钥和私钥
     *
     * @param publicKeyPath  公钥保存的文件路径
     * @param privateKeyPath 私钥保存的文件路径
     * @param charset        指定字符集，用于文件写入时的编码
     * @throws IllegalArgumentException 如果参数中存在null或空字符串，或者指定了无效的字符集
     */
    public static void createKey(String publicKeyPath, String privateKeyPath, String charset) {
        // 检查输入参数是否合法，如果存在null或空字符串，则抛出异常
        if (publicKeyPath == null || publicKeyPath.isEmpty() ||
                privateKeyPath == null || privateKeyPath.isEmpty() ||
                charset == null || charset.isEmpty()) {
            throw new IllegalArgumentException("Parameters cannot be null or empty.");
        }

        // 根据输入的字符集名称获取Charset对象
        Charset cs;
        try {
            cs = Charset.forName(charset);
        } catch (IllegalArgumentException e) {
            // 如果字符集无效，抛出异常
            throw new IllegalArgumentException("Invalid charset: " + charset, e);
        }

        try {
            // 实例化密钥生成器
            KeyPairGenerator rsa = KeyPairGenerator.getInstance(RSA_ALGORITHM);
            // 初始化密钥生成器为2048位
            rsa.initialize(RSA_KEY_SIZE);
            // 生成密钥对
            KeyPair keyPair = rsa.generateKeyPair();

            // 将公钥编码为Base64格式字符串并写入文件
            FileUtils.writeFileContent(publicKeyPath, new String(Base64.getEncoder().encode(keyPair.getPublic().getEncoded()), cs), charset);
            // 将私钥编码为Base64格式字符串并写入文件
            FileUtils.writeFileContent(privateKeyPath, new String(Base64.getEncoder().encode(keyPair.getPrivate().getEncoded()), cs), charset);

        } catch (NoSuchAlgorithmException e) {
            // 如果RSA算法不可用，记录错误日志
            log.error("Failed to generate RSA key pair: Algorithm not available.", e);
            throw new RuntimeException("Failed to generate RSA key pair: Algorithm not available.", e);
        }
    }


    /**
     * 根据公钥文件路径获取PublicKey对象
     *
     * @param publicKeyPath 公钥文件的路径
     * @param charset       读取公钥文件时使用的字符集
     * @return 成功则返回PublicKey对象，失败则返回null
     */
    public static PublicKey getPublicKey(String publicKeyPath, String... charset) {
        try {
            // 读取公钥文件内容
            String publicKeyStr = FileUtils.getFileContent(publicKeyPath, charset);
            // 对读取的公钥内容进行Base64解码
            byte[] decodedKey = Base64.getDecoder().decode(publicKeyStr);
            // 获取RSA算法的KeyFactory实例
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            // 根据解码后的公钥字节数组生成PublicKey对象
            return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            // RSA算法相关异常处理
            log.error("Failed to generate public key from the specified key specification.", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 根据私钥路径获取私钥对象
     *
     * @param privateKeyPath 私钥文件的路径
     * @param charset        文件内容的编码格式，如"UTF-8"
     * @return 返回PrivateKey对象，如果发生异常则返回null
     */
    public static PrivateKey getPrivateKey(String privateKeyPath, String charset) {
        try {
            // 读取私钥文件内容
            String privateKey = FileUtils.getFileContent(privateKeyPath, charset);
            // 对读取的内容进行Base64解码
            byte[] decode = Base64.getDecoder().decode(privateKey);
            // 创建RSA密钥工厂
            KeyFactory rsa = KeyFactory.getInstance(RSA_ALGORITHM);
            // 使用解码后的密钥材料生成私钥对象
            return rsa.generatePrivate(new PKCS8EncodedKeySpec(decode));
        } catch (Exception e) {
            // RSA算法相关异常处理
            log.error("Failed to generate private key from the specified key specification.", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 使用RSA公钥加密明文
     *
     * @param plainText 待加密的明文
     * @param publicKey 加密使用的公钥
     * @param charset   明文的字符集
     * @return 返回Base64编码的加密字符串
     */
    public static String encrypt(String plainText, PublicKey publicKey, String charset) {
        try {
            // 使用RSA算法的Cipher对象进行加密操作
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            // 初始化Cipher对象为加密模式
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            // 对明文进行加密
            byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(Charset.forName(charset)));
            // 将加密后的字节数组转换为Base64编码的字符串
            return Base64.getEncoder().encodeToString(encryptedBytes);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
                 BadPaddingException e) {
            log.error("Encryption failed {}", e.getMessage(), e);
            throw new RuntimeException("Failed to encrypt the plaintext", e);
        }
    }

    /**
     * 使用RSA算法和私钥对已加密的文本进行解密
     *
     * @param encryptedText 已加密的文本，使用Base64编码
     * @param privateKey    用于解密的私钥
     * @param charset       指定字符集，用于解码解密后的字节数组
     * @return 解密后的字符串
     */
    public static String decrypt(String encryptedText, PrivateKey privateKey, String charset) {
        try {
            // 创建Cipher实例，指定使用RSA算法
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            // 初始化Cipher为解密模式，并使用提供的私钥
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            // 使用Base64解码器将已加密的文本解码为字节数组
            byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText);
            // 使用Cipher解密字节数组
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
            // 使用指定的字符集解码解密后的字节数组，并返回字符串
            return new String(decryptedBytes, Charset.forName(charset));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
                 BadPaddingException e) {
            log.error("Error during decryption: " + e.getMessage(), e);
            throw new RuntimeException("Error during decryption", e);
        }
    }
}
